
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import ws.tracking.*;

/**
 * Utility class for converting an xml representation of a set
 * of routes into a data structure, and vice versa,
 */
public class RouteParser {

    /**
     * Structure that holds a set of routes and a list of
     * new orders.
     */
    public static class ParseResult {

        /**
         *
         */
        public List<Vehicle> routes;
        /**
         *
         */
        public List<Order> newOrders;
    }

    /**
     * Converts a set of routes into an XML representation.
     *
     * @param routes a set of routes
     * @param out the output stream on which to write the result
     * @throws ParserConfigurationException
     * @throws TransformerException
     */
    public static void createXml(List<Vehicle> routes, OutputStream out)
            throws ParserConfigurationException, TransformerException {
        createXml(routes, null, out);
    }

    /**
     * Converts a set of routes and a list of orders into an XML
     * representation.
     *
     * @param routes a set of routes
     * @param newOrders a list of new orders
     * @param out the output stream on which to write the result
     * @throws ParserConfigurationException
     * @throws TransformerException
     */
    public static void createXml(List<Vehicle> routes, List<Order> newOrders, OutputStream out)
            throws ParserConfigurationException, TransformerException {
        Document dom = createDomTree(routes, newOrders);
        Result result = new StreamResult(out);
        Transformer t = TransformerFactory.newInstance().newTransformer();
        t.setOutputProperty(OutputKeys.METHOD, "xml");
        t.setOutputProperty(OutputKeys.INDENT, "yes");
        t.transform(new DOMSource(dom), result);
    }

    /**
     * Converts an XML representation of a set of routes and new orders
     * into a pair of data structures.
     *
     * @param in the input stream from which to read the XML file
     * @return a pair of values consisting of a set of routes and
     *         a list of new orders
     * @throws ParserConfigurationException
     * @throws SAXException
     * @throws IOException
     */
    public static ParseResult parse(InputStream in)
            throws ParserConfigurationException, SAXException, IOException {

        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        ArrayList<Vehicle> routes = new ArrayList<Vehicle>();
        ArrayList<Order> newOrders;

        Document dom;
        DocumentBuilder db = dbf.newDocumentBuilder();
        dom = db.parse(in);

        Element root = dom.getDocumentElement();
        NodeList nl = root.getElementsByTagName("routes");
        if (nl != null && nl.getLength() > 0) {
            // should be only one element
            Element element = (Element) nl.item(0);
            nl = element.getElementsByTagName("vehicle");
            if (nl != null) {
                for (int i = 0; i < nl.getLength(); i++) {
                    Element v = (Element) nl.item(i);
                    Vehicle vehicle = constructVehicle(v);
                    routes.add(vehicle);
                }
            }
        }

        nl = root.getElementsByTagName("neworders");
        if (nl != null && nl.getLength() > 0) {
            // should be only one element
            Element element = (Element) nl.item(0);
            newOrders = createOrderList(element);
        } else {
            newOrders = new ArrayList<Order>();
        }

        ParseResult ret = new ParseResult();
        ret.routes = routes;
        ret.newOrders = newOrders;
        return ret;
    }

    //
    // Helper methods for creating data structure
    //
    private static Vehicle constructVehicle(Element element) {
        String idString = element.getAttribute("id");
        int id = Integer.parseInt(idString);
        String capacityString = element.getAttribute("capacity");
        double capacity = Double.parseDouble(capacityString);

        NodeList nl = element.getElementsByTagName("stop");
        ArrayList<Stop> stops = new ArrayList<Stop>();
        if (nl != null) {
            for (int i = 0; i < nl.getLength(); ++i) {
                Element s = (Element) nl.item(i);
                Stop stop = constructStop(s);
                stops.add(stop);
            }
        }

        Vehicle ret = new Vehicle();
        ret.setId(id);
        ret.setCapacity(capacity);

        for (Stop s : stops) {
            ret.getCurrentRoute().add(s);
        }
        return ret;
    }

    private static Stop constructStop(Element element) {
        String location = element.getAttribute("location");
        ArrayList<Order> pickups;
        ArrayList<Order> deliveries;

        NodeList nl = element.getElementsByTagName("pickup");
        if (nl != null && nl.getLength() > 0) {
            Element pickup = (Element) nl.item(0);
            pickups = createOrderList(pickup);
        } else {
            pickups = new ArrayList<Order>();
        }
        nl = element.getElementsByTagName("delivery");
        if (nl != null && nl.getLength() > 0) {
            Element delivery = (Element) nl.item(0);
            deliveries = createOrderList(delivery);
        } else {
            deliveries = new ArrayList<Order>();
        }

        Stop ret = new Stop();
        ret.setLocation(location);
        for (Order o : pickups) {
            ret.getPickups().add(o);
        }
        for (Order o : deliveries) {
            ret.getDeliveries().add(o);
        }
        return ret;
    }

    private static ArrayList<Order> createOrderList(Element element) {
        ArrayList<Order> orders = new ArrayList<Order>();
        NodeList orderList = element.getElementsByTagName("order");
        if (orderList != null) {
            for (int i = 0; i < orderList.getLength(); ++i) {
                Element orderElement = (Element) orderList.item(i);
                Order order = createOrder(orderElement);
                orders.add(order);
            }
        }

        return orders;
    }

    private static Order createOrder(Element element) throws NumberFormatException {
        String orderidString = element.getAttribute("orderid");
        int orderid = Integer.parseInt(orderidString);
        String customerid = element.getAttribute("customerid"); // not used
        String src = element.getAttribute("src");
        String dst = element.getAttribute("dst");
        String weightString = element.getAttribute("weight");
        double weight = Double.parseDouble(weightString);
        String deadlineString = element.getAttribute("deadline");
        SimpleDate deadline = createSimpleDate(deadlineString);
        Order ret = new Order();
        ret.setDeadline(deadline);
        ret.setDst(dst);
        ret.setId(orderid);
        ret.setSrc(src);
        ret.setWeight(weight);
        return ret;
    }

    // create SimpleDate from form "2008/12/05"
    private static SimpleDate createSimpleDate(String s) throws NumberFormatException {
        if (s != null) {
            String[] ss = s.split("/");
            if (ss.length == 3) {
                SimpleDate ret = new SimpleDate();
                ret.setDay(Integer.parseInt(ss[2]));
                ret.setMonth(Integer.parseInt(ss[1]));
                ret.setYear(Integer.parseInt(ss[0]));
                return ret;
            }
        }
        return new SimpleDate();
    }

    //
    // Helper methods for creating xml file
    //
    private static Document createDomTree(List<Vehicle> routes, List<Order> newOrders)
            throws ParserConfigurationException {
        DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
        Document dom = db.newDocument();
        Element root = dom.createElement("routedata");
        dom.appendChild(root);
        Element routeElement = dom.createElement("routes");
        root.appendChild(routeElement);
        if (routes != null) {
            for (Vehicle v : routes) {
                routeElement.appendChild(createVehicleElement(dom, v));
            }
        }

        Element newOrderElement = dom.createElement("neworders");
        root.appendChild(newOrderElement);
        if (newOrders != null) {
            for (Order order : newOrders) {
                newOrderElement.appendChild(createOrderElement(dom, order));
            }
        }
        return dom;
    }

    private static Element createVehicleElement(Document dom, Vehicle vehicle) {
        Element vehicleElement = dom.createElement("vehicle");
        vehicleElement.setAttribute("id", String.valueOf(vehicle.getId()));
        vehicleElement.setAttribute("capacity", String.valueOf(vehicle.getCapacity()));
        for (Stop s : vehicle.getCurrentRoute()) {
            vehicleElement.appendChild(createStopElement(dom, s));
        }
        return vehicleElement;
    }

    private static Element createStopElement(Document dom, Stop stop) {
        Element stopElement = dom.createElement("stop");
        stopElement.setAttribute("location", stop.getLocation());
        Element pickupElement = createOrderListElement(dom, stop.getPickups(), "pickup");
        stopElement.appendChild(pickupElement);
        Element deliveryElement = createOrderListElement(dom, stop.getDeliveries(), "delivery");
        stopElement.appendChild(deliveryElement);
        return stopElement;
    }

    private static Element createOrderListElement(Document dom, List<Order> orders, String name) {
        Element listElement = dom.createElement(name);
        for (Order order : orders) {
            Element orderElement = createOrderElement(dom, order);
            listElement.appendChild(orderElement);
        }
        return listElement;
    }

    private static Element createOrderElement(Document dom, Order order) {
        Element orderElement = dom.createElement("order");
        orderElement.setAttribute("orderid", String.valueOf(order.getId()));
        orderElement.setAttribute("customerid", String.valueOf(0)); // unknown
        orderElement.setAttribute("src", order.getSrc());
        orderElement.setAttribute("dst", order.getDst());
        orderElement.setAttribute("weight", String.valueOf(order.getWeight()));
        orderElement.setAttribute("deadline", createSimpleDateString(order.getDeadline()));
        return orderElement;
    }

    private static String createSimpleDateString(SimpleDate date) {
        return date.getYear() + "/" +
                String.format("%02d", date.getMonth()) + "/" +
                String.format("%02d", date.getDay());
    }

    private static void testOutput(Document dom) throws Exception {
        Result result = new StreamResult(System.out);
        Transformer t = TransformerFactory.newInstance().newTransformer();
        t.setOutputProperty(OutputKeys.METHOD, "xml");
        t.transform(new DOMSource(dom), result);
    }
}
